Workflow to ID motifs and match to genes

(Full implementation in Snakefile)

  1. Download TF motifs from cisbp
  2. Only keep TF which are abs(logFc) > 1 between GFP/RPE and iPSC (~1200)
  3. Check for motifs in narrow ATAC-seq peaks with fimo
  4. Identify closest 2 genes (under 500k bp) to each motif
  5. Bootstrap steps 2 and 3 250 times each to get background rate
# Load Libraries without printing any warnings or messages
library(tidyverse)
library(ggridges)
library(cowplot)
library(DESeq2)
library(ggrepel)

Motif/TF assessed

# Load in real data
samples_full = list.files('/Volumes/data/projects/nei/hufnagel/iPSC_RPE_ATAC_Seq/fimo_motifs/', pattern = '*dat.gz', full.names = T)
fimo_data <- list()
for (i in samples_full){
  sample <- str_split(str_split(i, '/')[[1]][10], '\\.')[[1]][1]
  input <- read_tsv(i)
  input <- input %>% mutate(sequence_name = as.character(sequence_name)) %>% mutate(sample = sample)
  fimo_data[[sample]] <- input
}
sample_motifs = bind_rows(fimo_data) %>% mutate(sample=as.factor(sample))
#sample_motifs %>% group_by(sample, motif_alt_id) %>% summarise(Count=n()) %>% arrange(-Count)
tf_motif <- sample_motifs %>% select('TF' = `# motif_id`, motif_alt_id) %>% unique()
tf_motif %>% DT::datatable()
# merge sample counts with bootstrap counts
all <- bind_rows(sample_motifs %>% group_by(sample, motif_alt_id) %>% summarise(Count=n()) %>% ungroup() %>% mutate(bootstrap = 'real'),
                 bootstrap_counts)
# motif ggridges plotter
plotter <- function(motif, scale=TRUE) {
  if (scale == TRUE){
    all <- all %>% filter(motif_alt_id == !!motif) %>% 
      group_by(sample, motif_alt_id) %>% 
      mutate(`Z score` = scale(Count)) %>% 
      ungroup() 
    all %>% 
      mutate(sample = factor(sample, levels=c('iPSC_IIi_9','iPSC_IIJ_10','iPSC_IIK_11','iPSC_IIL_12','RFP_IIE_1','RFP_IIF_2','RFP_IIG_3','RFP_IIH_4','GFP_IIE_5','GFP_IIF_6','GFP_IIG_7','GFP_IIH_8'))) %>% 
      ggplot(aes(x=`Z score`, y=sample)) + 
      geom_density_ridges() +
      geom_point(data = all %>% 
                   filter(bootstrap == 'real', motif_alt_id == !!motif) %>% 
                   ungroup(), aes(x=`Z score`,y=sample), colour='blue', size=2, alpha=0.5) +
      scale_y_discrete(expand = c(0.01, 0)) +
      theme_ridges() + 
      ggtitle(paste0(motif, ' (', 
                     tf_motif %>% filter(motif_alt_id == !!motif) %>% pull(TF),
                     ')'))
  }
  else {
    all %>% filter(motif_alt_id == !!motif) %>% 
      mutate(sample = factor(sample, levels=c('iPSC_IIi_9','iPSC_IIJ_10','iPSC_IIK_11','iPSC_IIL_12','RFP_IIE_1','RFP_IIF_2','RFP_IIG_3','RFP_IIH_4','GFP_IIE_5','GFP_IIF_6','GFP_IIG_7','GFP_IIH_8'))) %>% 
      ggplot(aes(x=Count, y=sample)) + 
      geom_density_ridges() +
      geom_point(data = sample_motifs %>% 
                   filter(motif_alt_id == !!motif) %>% 
                   group_by(sample, motif_alt_id) %>%
                   summarise(Count=n()) %>% ungroup(), aes(x=Count,y=sample), colour='blue', size=2, alpha=0.5) +
      scale_y_discrete(expand = c(0.01, 0)) +
      theme_ridges() + 
      ggtitle(paste0(motif, ' (', 
                     tf_motif %>% filter(motif_alt_id == !!motif) %>% pull(TF),
                     ')'))
  }
}

Plot enrichment specific motif/TFBS

TET1

plotter('M0610_1.02')

DNMT1

plotter('M0609_1.02')

PAX6

plotter('M6410_1.02')

SOX10

plotter('M6470_1.02')

MITF

plotter('M6345_1.02')

HES1

plotter('M6271_1.02')

SIX3

plotter('M1016_1.02')

SIX2

plotter('M0952_1.02')

Distribution of TFBS/motif enrichment

Fold Change

delta_motif_FC <- all %>% mutate(Type = case_when(grepl('GFP', sample) ~ 'GFP',
                                                    grepl('RFP', sample) ~ 'RFP',
                                                    TRUE ~ 'iPSC'),
                                   Boot = case_when(bootstrap == 'real' ~ 'real',
                                                    TRUE ~ 'boot')) %>% 
  group_by(Type, motif_alt_id, Boot) %>% 
  mutate(`Z score` = scale(Count)) %>% 
  summarise(Mean=mean(Count)) %>% 
  spread(Boot, Mean) %>%
  ungroup() %>% 
  mutate(FC = real / boot, Type = factor(Type, levels=c('iPSC','RFP','GFP'))) %>% 
  arrange(-FC) %>% 
  left_join(tf_motif) %>% 
  select(-boot, -real) 
Joining, by = "motif_alt_id"
delta_motif_FC %>% ggplot(aes(y=Type, x=log2(FC))) + geom_density_ridges() + theme_ridges() 

delta_motif_FC %>% ggplot(aes(y=Type, x=FC)) + geom_density_ridges() + theme_ridges() 

# calculate z scores for all
Z_scoring <- all %>% 
  # collapse by Type, motif, and whether bootstrap or real
  group_by(sample, motif_alt_id) %>% 
  mutate(`Z score` = scale(Count)) %>% 
  filter(bootstrap == 'real') %>% 
  select(-bootstrap, -TF) %>% 
  left_join(tf_motif)
Joining, by = "motif_alt_id"
Z_scoring %>% head()

Distribution of relative TFBS/motif enrichment between GFP <-> RFP

FC_motif_FC <- all %>% mutate(Type = case_when(grepl('GFP', sample) ~ 'GFP',
                                                    grepl('RFP', sample) ~ 'RFP',
                                                    TRUE ~ 'iPSC'),
                                   Boot = case_when(bootstrap == 'real' ~ 'real',
                                                    TRUE ~ 'boot')) %>% 
  # collapse by Type, motif, and whether bootstrap or real
  group_by(Type, motif_alt_id, Boot) %>% 
  summarise(Mean=mean(Count)) %>% 
  # go wide
  spread(Boot, Mean) %>%
  # so you can calculate FC
  mutate(FC = real / boot) %>% 
  arrange(-FC) %>% 
  # add motif name
  left_join(tf_motif) %>% 
  select(-boot, -real) %>%
  # spread again to allow for GFP vs RFP or iPSC comparisons
  spread(Type, FC) %>% 
  filter(GFP > 1.2) %>% 
  mutate(`FC^2 GFP <-> iPSC` = GFP / iPSC, `FC^2 GFP <-> RFP` = GFP / RFP) %>% 
  arrange(-`FC^2 GFP <-> RFP`) 
Joining, by = "motif_alt_id"
plot1 <- FC_motif_FC %>% 
  ggplot(aes(x=`FC^2 GFP <-> RFP`)) + 
  geom_density(fill='gray') + 
  theme_minimal() + 
  geom_vline(xintercept = 1.2, color='blue')
plot1

Distribution of relative TFBS/motif enrichment between GFP <-> iPSC

plot2 <- FC_motif_FC %>% 
  ggplot(aes(x=`FC^2 GFP <-> iPSC`)) + 
  geom_density(fill='gray') + 
  theme_minimal()+ 
  geom_vline(xintercept = 1.2, color = 'blue')
plot2 

Both together

plot_grid(plot1 + coord_cartesian(xlim=c(0.5,5.5)), plot2 + coord_cartesian(xlim=c(0.5,5.5)), nrow = 2, align = 'hv', axis='tblr')

DESeq2-based analysis

I realized that motifs by samples is similar to genes by samples

We are dropping RFP II E because from the plots, seems rarely enriched in anything

cts <- all %>% filter(bootstrap=='real') %>% spread(sample, Count) %>% select(-bootstrap, -TF) %>% data.frame()
row.names(cts) <- cts$motif_alt_id
cts %>% head()
cts <- cts[,-1]
coldata <- all %>% select(sample) %>% unique() %>% filter(sample!='RFP_IIE_1') %>% mutate(Type =  case_when(grepl('GFP', sample) ~ 'GFP',
                                                                            grepl('RFP', sample) ~ 'RFP',
                                                                            TRUE ~ 'iPSC'))
dds <- DESeqDataSetFromMatrix(countData = cts %>% select(one_of(as.character(coldata$sample)) ),
                              colData = coldata,
                              design = ~ Type)
some variables in design formula are characters, converting to factors
dds$Type <- relevel(dds$Type, ref='RFP')
dds <- DESeq(dds)
estimating size factors
estimating dispersions
gene-wise dispersion estimates
mean-dispersion relationship
-- note: fitType='parametric', but the dispersion trend was not well captured by the
   function: y = a/x + b, and a local regression fit was automatically substituted.
   specify fitType='local' or 'mean' to avoid this message next time.
final dispersion estimates
fitting model and testing
res <- results(DESeq(dds))
using pre-existing size factors
estimating dispersions
found already estimated dispersions, replacing these
gene-wise dispersion estimates
mean-dispersion relationship
-- note: fitType='parametric', but the dispersion trend was not well captured by the
   function: y = a/x + b, and a local regression fit was automatically substituted.
   specify fitType='local' or 'mean' to avoid this message next time.
final dispersion estimates
fitting model and testing
resLFC <- lfcShrink(dds, coef='Type_GFP_vs_RFP', type='apeglm')
using 'apeglm' for LFC shrinkage. If used in published research, please cite:
    Zhu, A., Ibrahim, J.G., Love, M.I. (2018) Heavy-tailed prior distributions for
    sequence count data: removing the noise and preserving large differences.
    bioRxiv. https://doi.org/10.1101/303255
plotMA(resLFC)

plot(hist(resLFC$pvalue))

Table of results

The Z score is the number of standard deviation of motifs found in the sample peaks above the random set of peaks

results <- resLFC %>% 
  data.frame() %>% 
  rownames_to_column('motif_alt_id') %>% 
  left_join(tf_motif) %>% 
  arrange(pvalue) %>% 
  data.frame() %>% 
  left_join(Z_scoring %>%  mutate(Type = case_when(grepl('GFP', sample) ~ 'GFP',
                          grepl('RFP', sample) ~ 'RFP',
                          TRUE ~ 'iPSC')) %>% 
              filter(Type=='GFP') %>% group_by(motif_alt_id) %>% summarise(`Z score` = mean(`Z score`))) %>% 
  left_join(gfp_rfp %>% mutate(TF=Gene, log2FC_GFP_RFP_RNASeq = log2FoldChange) %>% select(TF, log2FC_GFP_RFP_RNASeq)) 
Joining, by = "motif_alt_id"
Joining, by = "motif_alt_id"
Joining, by = "TF"
results <- resLFC %>% 
  data.frame() %>% 
  rownames_to_column('motif_alt_id') %>% 
  left_join(tf_motif) %>% 
  arrange(pvalue) %>% 
  data.frame() %>% 
  left_join(Z_scoring %>%  mutate(Type = case_when(grepl('GFP', sample) ~ 'GFP',
                          grepl('RFP', sample) ~ 'RFP',
                          TRUE ~ 'iPSC')) %>% 
              filter(Type=='GFP') %>% group_by(motif_alt_id) %>% summarise(`Z score` = mean(`Z score`))) %>% 
  left_join(gfp_rfp %>% mutate(TF=Gene, log2FC_GFP_RFP_RNASeq = log2FoldChange) %>% select(TF, log2FC_GFP_RFP_RNASeq)) 
Joining, by = "motif_alt_id"
Joining, by = "motif_alt_id"
Joining, by = "TF"
results %>% filter((`Z score`) > 2.5) %>% arrange(pvalue) %>% DT::datatable()

Volcano

volcano_maker <- function(df, title){
  df$Class <- 'Not significant'
  df$Class[df$padj < 0.01 & df$log2FoldChange > 0.4 & (df$`Z score`) > 2.5] <- "FDR < 0.01 &\nabs(Z score) > 2.5"
  df$Class[df$padj < 0.01 & df$log2FoldChange < -0.4 & (df$`Z score`) > 2.5] <- "FDR < 0.01 &\nabs(Z score) > 2.5"
  df$Class <- factor(df$Class, levels=c('Not significant', "FDR < 0.01 &\nabs(Z score) > 2.5"))
  plot <- ggplot(data=df,aes(x=log2FoldChange,y=-log10(pvalue))) + 
    geom_point(aes(colour=Class, size=`Z score`), alpha=0.5) +
    scale_colour_manual(values=c("gray","darkred")) + 
    geom_text_repel(data=df %>% filter((padj < 0.01 & log2FoldChange > 0.4 & `Z score` > 2.5) | (padj < 0.01 & log2FoldChange < -0.4 & `Z score` > 2.5)), 
                    aes(label=TF)) +
    # geom_vline(aes(xintercept=-0.5),linetype="dotted") +
    # geom_vline(aes(xintercept=0.5),linetype="dotted") +
    scale_x_continuous(breaks=c(-1,-0.5,0,0.5,1)) +
    ggtitle(title) + theme_minimal()
  return(plot)
}
volcano_maker(results, 'GFP vs RFP TFBS motif counts')

Plots of the top motifs different between GFP and RFP

RFP II E is plotted here, but not used in the differential analysis above

plots <- list()
for (i in results %>% filter((`Z score`) > 2) %>% arrange(pvalue) %>% head(n=12) %>% pull(motif_alt_id)){
  plots[[i]] <- plotter(i, scale=T)
}
plot_grid(plotlist = plots, ncol=3)
Picking joint bandwidth of 0.247
Picking joint bandwidth of 0.26
Picking joint bandwidth of 0.26
Picking joint bandwidth of 0.263
Picking joint bandwidth of 0.252
Picking joint bandwidth of 0.252
Picking joint bandwidth of 0.252
Picking joint bandwidth of 0.268
Picking joint bandwidth of 0.274
Picking joint bandwidth of 0.268
Picking joint bandwidth of 0.258
Picking joint bandwidth of 0.28

# Gene <-> motif matching
# load gtf to map transcript to gene_name

gtf <- read_tsv("/Volumes/data/genomes/1000G_phase2_GRCh37/gencode.v28lift37.metadata.HGNC.gz", col_names = c('Transcript','Gene'))
# gtf <- gtf %>% rowwise() %>% mutate(transcript = ((str_split(V9, ';')[[1]] %>% str_extract('transcript_id.*') %>% na.omit())[1] %>% str_split(., '\"'))[[1]][2],
#                              gene = ((str_split(V9, ';')[[1]] %>% str_extract('gene_name.*') %>% na.omit())[1] %>% str_split(., '\"'))[[1]][2])
## Sample data
### Closest TSS for OTX2 (M5700_1.02)

# samples_full = list.files('/Volumes/data/projects/nei/hufnagel/iPSC_RPE_ATAC_Seq/closest_TSS_motifs/', 
#                           pattern = '*dat.gz', full.names = T)
# 
# closestTSS_data <- list()
# for (i in samples_full){
#   sample <- str_split(str_split(i, '/')[[1]][10], '\\.')[[1]][1]
#  # input <- read_tsv(i, col_names = c('sequence_name', 'start', 'end', 'motif', 'fimo_pvalue', 'strand', 'tss_seq','tss_start','tss_end', 'transcript',  'blank','tss_strand','coord1','coord2','blank2','exon_num','size','exon_pos','distance'))
#   input <- fread(paste('gzcat', i, '| grep M5700_1'))
#   colnames(input) <- c('sequence_name', 'start', 'end', 'motif', 'fimo_pvalue', 'strand', 'tss_seq','tss_start','tss_end', 'transcript',  'blank','tss_strand','coord1','coord2','blank2','exon_num','size','exon_pos','distance')
#   input <- input %>% mutate(sequence_name = as.character(sequence_name),
#                             tss_seq = as.character(tss_seq),
#                             coord1=as.numeric(coord1),
#                             coord2=as.numeric(coord2),
#                             blank2 = as.character(blank2),
#                             exon_num=as.integer(exon_num),
#                             size=as.numeric(size)) %>% mutate(sample = sample)
#   closestTSS_data[[sample]] <- input
# }
# sample_closestTSS = bind_rows(closestTSS_data) %>% mutate(sample=as.factor(sample)) %>% rowwise() %>% mutate(Transcript = str_split(transcript, '_')[[1]][1])
# 
# both <- left_join(sample_closestTSS, gtf) %>% 
#   filter(!is.na(Gene)) %>% 
#   mutate(motif_loc = paste(sequence_name, start, end, sep='_'))
# 
# both %>% 
#   # remove any TSS over 500kb away
#   filter(distance < 500000) %>% 
#   #  one gene per motif
#   group_by(motif_loc, sample, Gene) %>% 
#   top_n(1, distance) %>% 
#   ungroup() %>% 
#   # add up to two genes (total) per motif
#   group_by(motif_loc, sample) %>% 
#   top_n(2, distance) %>% 
#   ungroup() %>% 
#   # arrange by genes most linked to motif  
#   group_by(Gene, sample) %>% 
#   summarise(Count=n(), paste(motif_loc, collapse=', ')) %>% 
#   arrange(-Count)

# What genes have more PAX6 'associated' motifs compared from GFP to RFP

# enriched_genes <- both %>% 
#   # only keep one gene per motif
#   group_by(motif_loc, sample, Gene) %>% 
#   top_n(1, distance) %>% 
#   ungroup() %>% 
#   # keep up to two genes per motif
#   group_by(motif_loc, sample) %>% 
#   top_n(2, distance) %>% 
#   ungroup() %>% 
#   # arrange by genes most linked to motif  
#   group_by(Gene, sample) %>% 
#   summarise(Count=n(), paste(motif_loc, collapse=', ')) %>% 
#   ungroup() %>% 
#   # collapse to GFP/RFP/iPSC
#   mutate(Type = case_when(grepl('GFP', sample) ~ 'GFP',
#                           grepl('RFP', sample) ~ 'RFP',
#                           TRUE ~ 'iPSC')) %>% 
#   group_by(Gene, Type) %>% 
#   summarise(Total=sum(Count)) %>% 
#   arrange(-Total) %>% 
#   spread(Gene, Total) %>% t() 
# 
# colnames(enriched_genes) <- enriched_genes[1,]
# enriched_genes <- enriched_genes[-1,] %>% data.frame() %>% rownames_to_column('Gene') %>%  mutate(GFP = as.numeric(GFP), iPSC = as.numeric(iPSC), RFP = as.numeric((RFP)))
# 
# enriched_genes[is.na(enriched_genes)] <- 0
# 
# enriched_genes %>% mutate(`deltaGFP <-> RFP` = GFP - RFP) %>% arrange(-`deltaGFP <-> RFP`, GFP) %>% head(1000) %>% DT::datatable(rownames = F)
## does PAX6 regulate PAX6?
#Yes, yes it does.

#GFP specific!
  
# both %>% 
#   # only keep one gene per motif
#   group_by(motif_loc, sample, Gene) %>% 
#   top_n(1, distance) %>% 
#   ungroup() %>% 
#   # keep up to two genes per motif
#   group_by(motif_loc, sample) %>% 
#   top_n(2, distance) %>% 
#   ungroup() %>% 
#   # arrange by genes most linked to motif  
#   group_by(Gene, sample) %>% 
#   summarise(Count=n(), `Motif Locations` = paste(motif_loc, collapse=', ')) %>% 
#   arrange(-Count) %>% 
#   filter(Gene=='PAX6')

```

LS0tCnRpdGxlOiBNb3RpZiAvIFRGQlMgQW5hbHlzaXMKYXV0aG9yOiBEYXZpZCBNY0dhdWdoZXkKZGF0ZTogJ2ByIGZvcm1hdChTeXMuRGF0ZSgpLCAiJVktJW0tJWQiKWAnCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvYzogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKIyBXb3JrZmxvdyB0byBJRCBtb3RpZnMgYW5kIG1hdGNoIHRvIGdlbmVzCgooRnVsbCBpbXBsZW1lbnRhdGlvbiBpbiBTbmFrZWZpbGUpCgoxLiBEb3dubG9hZCBURiBtb3RpZnMgZnJvbSBjaXNicAoyLiBPbmx5IGtlZXAgVEYgd2hpY2ggYXJlIGFicyhsb2dGYykgPiAxIGJldHdlZW4gR0ZQL1JQRSBhbmQgaVBTQyAofjEyMDApCjMuIENoZWNrIGZvciBtb3RpZnMgaW4gbmFycm93IEFUQUMtc2VxIHBlYWtzIHdpdGggZmltbwo0LiBJZGVudGlmeSBjbG9zZXN0IDIgZ2VuZXMgKHVuZGVyIDUwMGsgYnApIHRvIGVhY2ggbW90aWYKNS4gQm9vdHN0cmFwIHN0ZXBzIDIgYW5kIDMgMjUwIHRpbWVzIGVhY2ggdG8gZ2V0IGJhY2tncm91bmQgcmF0ZQoKYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GLCByZXN1bHRzPSdoaWRlJ30KIyBMb2FkIExpYnJhcmllcyB3aXRob3V0IHByaW50aW5nIGFueSB3YXJuaW5ncyBvciBtZXNzYWdlcwpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ3JpZGdlcykKbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KERFU2VxMikKbGlicmFyeShnZ3JlcGVsKQpgYGAKCiMgTW90aWYvVEYgYXNzZXNzZWQKYGBge3IsIHJlc3VsdHMgPSAnaGlkZSd9CiMgTG9hZCBpbiByZWFsIGRhdGEKc2FtcGxlc19mdWxsID0gbGlzdC5maWxlcygnL1ZvbHVtZXMvZGF0YS9wcm9qZWN0cy9uZWkvaHVmbmFnZWwvaVBTQ19SUEVfQVRBQ19TZXEvZmltb19tb3RpZnMvJywgcGF0dGVybiA9ICcqZGF0Lmd6JywgZnVsbC5uYW1lcyA9IFQpCgpmaW1vX2RhdGEgPC0gbGlzdCgpCmZvciAoaSBpbiBzYW1wbGVzX2Z1bGwpewogIHNhbXBsZSA8LSBzdHJfc3BsaXQoc3RyX3NwbGl0KGksICcvJylbWzFdXVsxMF0sICdcXC4nKVtbMV1dWzFdCiAgaW5wdXQgPC0gcmVhZF90c3YoaSkKICBpbnB1dCA8LSBpbnB1dCAlPiUgbXV0YXRlKHNlcXVlbmNlX25hbWUgPSBhcy5jaGFyYWN0ZXIoc2VxdWVuY2VfbmFtZSkpICU+JSBtdXRhdGUoc2FtcGxlID0gc2FtcGxlKQogIGZpbW9fZGF0YVtbc2FtcGxlXV0gPC0gaW5wdXQKfQpzYW1wbGVfbW90aWZzID0gYmluZF9yb3dzKGZpbW9fZGF0YSkgJT4lIG11dGF0ZShzYW1wbGU9YXMuZmFjdG9yKHNhbXBsZSkpCgojc2FtcGxlX21vdGlmcyAlPiUgZ3JvdXBfYnkoc2FtcGxlLCBtb3RpZl9hbHRfaWQpICU+JSBzdW1tYXJpc2UoQ291bnQ9bigpKSAlPiUgYXJyYW5nZSgtQ291bnQpCgp0Zl9tb3RpZiA8LSBzYW1wbGVfbW90aWZzICU+JSBzZWxlY3QoJ1RGJyA9IGAjIG1vdGlmX2lkYCwgbW90aWZfYWx0X2lkKSAlPiUgdW5pcXVlKCkKdGZfbW90aWYgJT4lIERUOjpkYXRhdGFibGUoKQpgYGAKCgpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUYsIGVjaG89RiwgcmVzdWx0cz0naGlkZSd9CiMgTG9hZCBpbiBib290c3RyYXBzCmJvb3RzdHJhcF9zdGF0cyA9IGxpc3QuZmlsZXMoJy9Wb2x1bWVzL2RhdGEvcHJvamVjdHMvbmVpL2h1Zm5hZ2VsL2lQU0NfUlBFX0FUQUNfU2VxL2ZpbW9fbW90aWZzL2Jvb3RzdHJhcHBpbmdfc3RhdHMvJywgcGF0dGVybiA9ICcqZGF0Lmd6JywgZnVsbC5uYW1lcyA9IFQpCiNzYW1wbGVzX2Z1bGwgPSBsaXN0LmZpbGVzKCd+L0Rlc2t0b3AvZmltb19tb3RpZnMvYm9vdHN0cmFwcGluZy8nLCBwYXR0ZXJuID0gJypkYXQuZ3onLCBmdWxsLm5hbWVzID0gVCkKCiMgYmlnIGRpZmZlcmVuY2UgaGVyZSB2cyBhYm92ZQojIG5vdCBzYXZpbmcgZnVsbCBmaWxlLCBhcyBlYWNoIGJvb3RzdHJhcCBpcyB+MjAwbWIgQ09NUFJFU1NFRAojIGp1c3Qgc2F2aW5nIHRoZSBzdGF0cwojIHJpZ2h0IG5vdywgY291bnRzIGJ5IG1vdGlmL3NhbXBsZQpmaW1vX2RhdGEgPC0gbGlzdCgpCmZvciAoaSBpbiBib290c3RyYXBfc3RhdHMpewogIHNhbXBsZSA8LSBzdHJfc3BsaXQoc3RyX3NwbGl0KGksICcvJylbWzFdXVsxMV0sICdcXC4nKVtbMV1dWzFdCiAgYm9vdHN0cmFwX251bSA8LSBzdHJfc3BsaXQoc3RyX3NwbGl0KHN0cl9zcGxpdChpLCAnLycpW1sxXV1bMTFdLCAnXFwuJylbWzFdXVsyXSwgJ18nKVtbMV1dWzJdCiAgc2FtcGxlX2Jvb3QgPC0gcGFzdGUwKHNhbXBsZSwnXycsYm9vdHN0cmFwX251bSkKICBzdGF0cyA8LSByZWFkX3RzdihpKSAlPiUgbXV0YXRlKHNhbXBsZSA9IGFzLmZhY3RvcihzYW1wbGUpLCBib290c3RyYXAgPSBib290c3RyYXBfbnVtKQogIGZpbW9fZGF0YVtbc2FtcGxlX2Jvb3RdXSA8LSBzdGF0cwp9CjEKYm9vdHN0cmFwX2NvdW50cyA9IGJpbmRfcm93cyhmaW1vX2RhdGEpICU+JSBtdXRhdGUoc2FtcGxlPWFzLmZhY3RvcihzYW1wbGUpKSAlPiUgbGVmdF9qb2luKHRmX21vdGlmKQpgYGAKCmBgYHtyfQojIG1lcmdlIHNhbXBsZSBjb3VudHMgd2l0aCBib290c3RyYXAgY291bnRzCmFsbCA8LSBiaW5kX3Jvd3Moc2FtcGxlX21vdGlmcyAlPiUgZ3JvdXBfYnkoc2FtcGxlLCBtb3RpZl9hbHRfaWQpICU+JSBzdW1tYXJpc2UoQ291bnQ9bigpKSAlPiUgdW5ncm91cCgpICU+JSBtdXRhdGUoYm9vdHN0cmFwID0gJ3JlYWwnKSwKICAgICAgICAgICAgICAgICBib290c3RyYXBfY291bnRzKQoKIyBtb3RpZiBnZ3JpZGdlcyBwbG90dGVyCgpwbG90dGVyIDwtIGZ1bmN0aW9uKG1vdGlmLCBzY2FsZT1UUlVFKSB7CiAgaWYgKHNjYWxlID09IFRSVUUpewogICAgYWxsIDwtIGFsbCAlPiUgZmlsdGVyKG1vdGlmX2FsdF9pZCA9PSAhIW1vdGlmKSAlPiUgCiAgICAgIGdyb3VwX2J5KHNhbXBsZSwgbW90aWZfYWx0X2lkKSAlPiUgCiAgICAgIG11dGF0ZShgWiBzY29yZWAgPSBzY2FsZShDb3VudCkpICU+JSAKICAgICAgdW5ncm91cCgpIAogICAgYWxsICU+JSAKICAgICAgbXV0YXRlKHNhbXBsZSA9IGZhY3RvcihzYW1wbGUsIGxldmVscz1jKCdpUFNDX0lJaV85JywnaVBTQ19JSUpfMTAnLCdpUFNDX0lJS18xMScsJ2lQU0NfSUlMXzEyJywnUkZQX0lJRV8xJywnUkZQX0lJRl8yJywnUkZQX0lJR18zJywnUkZQX0lJSF80JywnR0ZQX0lJRV81JywnR0ZQX0lJRl82JywnR0ZQX0lJR183JywnR0ZQX0lJSF84JykpKSAlPiUgCiAgICAgIGdncGxvdChhZXMoeD1gWiBzY29yZWAsIHk9c2FtcGxlKSkgKyAKICAgICAgZ2VvbV9kZW5zaXR5X3JpZGdlcygpICsKICAgICAgZ2VvbV9wb2ludChkYXRhID0gYWxsICU+JSAKICAgICAgICAgICAgICAgICAgIGZpbHRlcihib290c3RyYXAgPT0gJ3JlYWwnLCBtb3RpZl9hbHRfaWQgPT0gISFtb3RpZikgJT4lIAogICAgICAgICAgICAgICAgICAgdW5ncm91cCgpLCBhZXMoeD1gWiBzY29yZWAseT1zYW1wbGUpLCBjb2xvdXI9J2JsdWUnLCBzaXplPTIsIGFscGhhPTAuNSkgKwogICAgICBzY2FsZV95X2Rpc2NyZXRlKGV4cGFuZCA9IGMoMC4wMSwgMCkpICsKICAgICAgdGhlbWVfcmlkZ2VzKCkgKyAKICAgICAgZ2d0aXRsZShwYXN0ZTAobW90aWYsICcgKCcsIAogICAgICAgICAgICAgICAgICAgICB0Zl9tb3RpZiAlPiUgZmlsdGVyKG1vdGlmX2FsdF9pZCA9PSAhIW1vdGlmKSAlPiUgcHVsbChURiksCiAgICAgICAgICAgICAgICAgICAgICcpJykpCiAgfQogIGVsc2UgewogICAgYWxsICU+JSBmaWx0ZXIobW90aWZfYWx0X2lkID09ICEhbW90aWYpICU+JSAKICAgICAgbXV0YXRlKHNhbXBsZSA9IGZhY3RvcihzYW1wbGUsIGxldmVscz1jKCdpUFNDX0lJaV85JywnaVBTQ19JSUpfMTAnLCdpUFNDX0lJS18xMScsJ2lQU0NfSUlMXzEyJywnUkZQX0lJRV8xJywnUkZQX0lJRl8yJywnUkZQX0lJR18zJywnUkZQX0lJSF80JywnR0ZQX0lJRV81JywnR0ZQX0lJRl82JywnR0ZQX0lJR183JywnR0ZQX0lJSF84JykpKSAlPiUgCiAgICAgIGdncGxvdChhZXMoeD1Db3VudCwgeT1zYW1wbGUpKSArIAogICAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKCkgKwogICAgICBnZW9tX3BvaW50KGRhdGEgPSBzYW1wbGVfbW90aWZzICU+JSAKICAgICAgICAgICAgICAgICAgIGZpbHRlcihtb3RpZl9hbHRfaWQgPT0gISFtb3RpZikgJT4lIAogICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkoc2FtcGxlLCBtb3RpZl9hbHRfaWQpICU+JQogICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKENvdW50PW4oKSkgJT4lIHVuZ3JvdXAoKSwgYWVzKHg9Q291bnQseT1zYW1wbGUpLCBjb2xvdXI9J2JsdWUnLCBzaXplPTIsIGFscGhhPTAuNSkgKwogICAgICBzY2FsZV95X2Rpc2NyZXRlKGV4cGFuZCA9IGMoMC4wMSwgMCkpICsKICAgICAgdGhlbWVfcmlkZ2VzKCkgKyAKICAgICAgZ2d0aXRsZShwYXN0ZTAobW90aWYsICcgKCcsIAogICAgICAgICAgICAgICAgICAgICB0Zl9tb3RpZiAlPiUgZmlsdGVyKG1vdGlmX2FsdF9pZCA9PSAhIW1vdGlmKSAlPiUgcHVsbChURiksCiAgICAgICAgICAgICAgICAgICAgICcpJykpCiAgfQp9CmBgYAojIFBsb3QgZW5yaWNobWVudCAgc3BlY2lmaWMgbW90aWYvVEZCUwoKIyMgVEVUMQpgYGB7cn0KcGxvdHRlcignTTA2MTBfMS4wMicpCmBgYAoKIyMgRE5NVDEKYGBge3J9CnBsb3R0ZXIoJ00wNjA5XzEuMDInKQpgYGAKCiMjIFBBWDYKYGBge3J9CnBsb3R0ZXIoJ002NDEwXzEuMDInKQpgYGAKCiMjIFNPWDEwCmBgYHtyfQpwbG90dGVyKCdNNjQ3MF8xLjAyJykKYGBgCgojIyBNSVRGCmBgYHtyfQpwbG90dGVyKCdNNjM0NV8xLjAyJykKYGBgCgojIyBIRVMxCmBgYHtyfQpwbG90dGVyKCdNNjI3MV8xLjAyJykKYGBgCgojIyBTSVgzCmBgYHtyfQpwbG90dGVyKCdNMTAxNl8xLjAyJykKYGBgCgojIyBTSVgyCmBgYHtyfQpwbG90dGVyKCdNMDk1Ml8xLjAyJykKYGBgCgojIERpc3RyaWJ1dGlvbiBvZiBURkJTL21vdGlmIGVucmljaG1lbnQKIyMgRm9sZCBDaGFuZ2UKYGBge3J9CmRlbHRhX21vdGlmX0ZDIDwtIGFsbCAlPiUgbXV0YXRlKFR5cGUgPSBjYXNlX3doZW4oZ3JlcGwoJ0dGUCcsIHNhbXBsZSkgfiAnR0ZQJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyZXBsKCdSRlAnLCBzYW1wbGUpIH4gJ1JGUCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gJ2lQU0MnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBCb290ID0gY2FzZV93aGVuKGJvb3RzdHJhcCA9PSAncmVhbCcgfiAncmVhbCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gJ2Jvb3QnKSkgJT4lIAogIGdyb3VwX2J5KFR5cGUsIG1vdGlmX2FsdF9pZCwgQm9vdCkgJT4lIAogIG11dGF0ZShgWiBzY29yZWAgPSBzY2FsZShDb3VudCkpICU+JSAKICBzdW1tYXJpc2UoTWVhbj1tZWFuKENvdW50KSkgJT4lIAogIHNwcmVhZChCb290LCBNZWFuKSAlPiUKICB1bmdyb3VwKCkgJT4lIAogIG11dGF0ZShGQyA9IHJlYWwgLyBib290LCBUeXBlID0gZmFjdG9yKFR5cGUsIGxldmVscz1jKCdpUFNDJywnUkZQJywnR0ZQJykpKSAlPiUgCiAgYXJyYW5nZSgtRkMpICU+JSAKICBsZWZ0X2pvaW4odGZfbW90aWYpICU+JSAKICBzZWxlY3QoLWJvb3QsIC1yZWFsKSAKCmRlbHRhX21vdGlmX0ZDICU+JSBnZ3Bsb3QoYWVzKHk9VHlwZSwgeD1sb2cyKEZDKSkpICsgZ2VvbV9kZW5zaXR5X3JpZGdlcygpICsgdGhlbWVfcmlkZ2VzKCkgCgpkZWx0YV9tb3RpZl9GQyAlPiUgZ2dwbG90KGFlcyh5PVR5cGUsIHg9RkMpKSArIGdlb21fZGVuc2l0eV9yaWRnZXMoKSArIHRoZW1lX3JpZGdlcygpIApgYGAKCgoKYGBge3J9CiMgY2FsY3VsYXRlIHogc2NvcmVzIGZvciBhbGwKWl9zY29yaW5nIDwtIGFsbCAlPiUgCiAgIyBjb2xsYXBzZSBieSBUeXBlLCBtb3RpZiwgYW5kIHdoZXRoZXIgYm9vdHN0cmFwIG9yIHJlYWwKICBncm91cF9ieShzYW1wbGUsIG1vdGlmX2FsdF9pZCkgJT4lIAogIG11dGF0ZShgWiBzY29yZWAgPSBzY2FsZShDb3VudCkpICU+JSAKICBmaWx0ZXIoYm9vdHN0cmFwID09ICdyZWFsJykgJT4lIAogIHNlbGVjdCgtYm9vdHN0cmFwLCAtVEYpICU+JSAKICBsZWZ0X2pvaW4odGZfbW90aWYpCgpaX3Njb3JpbmcgJT4lIGhlYWQoKQpgYGAKIyBEaXN0cmlidXRpb24gb2YgKnJlbGF0aXZlKiBURkJTL21vdGlmIGVucmljaG1lbnQgKmJldHdlZW4qIEdGUCA8LT4gUkZQCmBgYHtyfQpGQ19tb3RpZl9GQyA8LSBhbGwgJT4lIG11dGF0ZShUeXBlID0gY2FzZV93aGVuKGdyZXBsKCdHRlAnLCBzYW1wbGUpIH4gJ0dGUCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncmVwbCgnUkZQJywgc2FtcGxlKSB+ICdSRlAnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICdpUFNDJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQm9vdCA9IGNhc2Vfd2hlbihib290c3RyYXAgPT0gJ3JlYWwnIH4gJ3JlYWwnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICdib290JykpICU+JSAKICAjIGNvbGxhcHNlIGJ5IFR5cGUsIG1vdGlmLCBhbmQgd2hldGhlciBib290c3RyYXAgb3IgcmVhbAogIGdyb3VwX2J5KFR5cGUsIG1vdGlmX2FsdF9pZCwgQm9vdCkgJT4lIAogIHN1bW1hcmlzZShNZWFuPW1lYW4oQ291bnQpKSAlPiUgCiAgIyBnbyB3aWRlCiAgc3ByZWFkKEJvb3QsIE1lYW4pICU+JQogICMgc28geW91IGNhbiBjYWxjdWxhdGUgRkMKICBtdXRhdGUoRkMgPSByZWFsIC8gYm9vdCkgJT4lIAogIGFycmFuZ2UoLUZDKSAlPiUgCiAgIyBhZGQgbW90aWYgbmFtZQogIGxlZnRfam9pbih0Zl9tb3RpZikgJT4lIAogIHNlbGVjdCgtYm9vdCwgLXJlYWwpICU+JQogICMgc3ByZWFkIGFnYWluIHRvIGFsbG93IGZvciBHRlAgdnMgUkZQIG9yIGlQU0MgY29tcGFyaXNvbnMKICBzcHJlYWQoVHlwZSwgRkMpICU+JSAKICBmaWx0ZXIoR0ZQID4gMS4yKSAlPiUgCiAgbXV0YXRlKGBGQ14yIEdGUCA8LT4gaVBTQ2AgPSBHRlAgLyBpUFNDLCBgRkNeMiBHRlAgPC0+IFJGUGAgPSBHRlAgLyBSRlApICU+JSAKICBhcnJhbmdlKC1gRkNeMiBHRlAgPC0+IFJGUGApIAoKcGxvdDEgPC0gRkNfbW90aWZfRkMgJT4lIAogIGdncGxvdChhZXMoeD1gRkNeMiBHRlAgPC0+IFJGUGApKSArIAogIGdlb21fZGVuc2l0eShmaWxsPSdncmF5JykgKyAKICB0aGVtZV9taW5pbWFsKCkgKyAKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAxLjIsIGNvbG9yPSdibHVlJykKcGxvdDEKYGBgCgojIERpc3RyaWJ1dGlvbiBvZiAqcmVsYXRpdmUqIFRGQlMvbW90aWYgZW5yaWNobWVudCAqYmV0d2VlbiogR0ZQIDwtPiBpUFNDCmBgYHtyfQpwbG90MiA8LSBGQ19tb3RpZl9GQyAlPiUgCiAgZ2dwbG90KGFlcyh4PWBGQ14yIEdGUCA8LT4gaVBTQ2ApKSArIAogIGdlb21fZGVuc2l0eShmaWxsPSdncmF5JykgKyAKICB0aGVtZV9taW5pbWFsKCkrIAogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDEuMiwgY29sb3IgPSAnYmx1ZScpCnBsb3QyIApgYGAKCiMjIEJvdGggdG9nZXRoZXIKYGBge3J9CnBsb3RfZ3JpZChwbG90MSArIGNvb3JkX2NhcnRlc2lhbih4bGltPWMoMC41LDUuNSkpLCBwbG90MiArIGNvb3JkX2NhcnRlc2lhbih4bGltPWMoMC41LDUuNSkpLCBucm93ID0gMiwgYWxpZ24gPSAnaHYnLCBheGlzPSd0YmxyJykKYGBgCgojIERFU2VxMi1iYXNlZCBhbmFseXNpcwpJIHJlYWxpemVkIHRoYXQgbW90aWZzIGJ5IHNhbXBsZXMgaXMgc2ltaWxhciB0byBnZW5lcyBieSBzYW1wbGVzCgpXZSBhcmUgZHJvcHBpbmcgUkZQIElJIEUgYmVjYXVzZSBmcm9tIHRoZSBwbG90cywgc2VlbXMgcmFyZWx5IGVucmljaGVkIGluIGFueXRoaW5nCmBgYHtyfQpjdHMgPC0gYWxsICU+JSBmaWx0ZXIoYm9vdHN0cmFwPT0ncmVhbCcpICU+JSBzcHJlYWQoc2FtcGxlLCBDb3VudCkgJT4lIHNlbGVjdCgtYm9vdHN0cmFwLCAtVEYpICU+JSBkYXRhLmZyYW1lKCkKcm93Lm5hbWVzKGN0cykgPC0gY3RzJG1vdGlmX2FsdF9pZApjdHMgJT4lIGhlYWQoKQpjdHMgPC0gY3RzWywtMV0KCmNvbGRhdGEgPC0gYWxsICU+JSBzZWxlY3Qoc2FtcGxlKSAlPiUgdW5pcXVlKCkgJT4lIGZpbHRlcihzYW1wbGUhPSdSRlBfSUlFXzEnKSAlPiUgbXV0YXRlKFR5cGUgPSAgY2FzZV93aGVuKGdyZXBsKCdHRlAnLCBzYW1wbGUpIH4gJ0dGUCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncmVwbCgnUkZQJywgc2FtcGxlKSB+ICdSRlAnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICdpUFNDJykpCgpkZHMgPC0gREVTZXFEYXRhU2V0RnJvbU1hdHJpeChjb3VudERhdGEgPSBjdHMgJT4lIHNlbGVjdChvbmVfb2YoYXMuY2hhcmFjdGVyKGNvbGRhdGEkc2FtcGxlKSkgKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sRGF0YSA9IGNvbGRhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlc2lnbiA9IH4gVHlwZSkKZGRzJFR5cGUgPC0gcmVsZXZlbChkZHMkVHlwZSwgcmVmPSdSRlAnKQpkZHMgPC0gREVTZXEoZGRzKQoKcmVzIDwtIHJlc3VsdHMoREVTZXEoZGRzKSkKcmVzTEZDIDwtIGxmY1NocmluayhkZHMsIGNvZWY9J1R5cGVfR0ZQX3ZzX1JGUCcsIHR5cGU9J2FwZWdsbScpCnBsb3RNQShyZXNMRkMpCnBsb3QoaGlzdChyZXNMRkMkcHZhbHVlKSkKCmBgYAoKYGBge3J9CiMgTG9hZCBpbiBSTkEtc2VxIGRhdGEKZ2ZwX3JmcCA8LSByZWFkX2Nzdignfi9naXQvaXBzY19ycGVfUk5BLXNlcS9kYXRhL0dGUF92c19SRlAucmVzdWx0cy5jc3YnKQpycGVfaXBzYyA8LSByZWFkX2Nzdignfi9naXQvaXBzY19ycGVfUk5BLXNlcS9kYXRhL1JQRV92c19pUFNDLnJlc3VsdHMuY3N2JykKZ2ZwX3JmcCAlPiUgaGVhZCgpCmBgYAoKIyMgVGFibGUgb2YgcmVzdWx0cwpUaGUgYFogc2NvcmVgIGlzIHRoZSBudW1iZXIgb2Ygc3RhbmRhcmQgZGV2aWF0aW9uIG9mIG1vdGlmcyBmb3VuZCBpbiB0aGUgc2FtcGxlIHBlYWtzIGFib3ZlIHRoZSByYW5kb20gc2V0IG9mIHBlYWtzCmBgYHtyfQpyZXN1bHRzIDwtIHJlc0xGQyAlPiUgCiAgZGF0YS5mcmFtZSgpICU+JSAKICByb3duYW1lc190b19jb2x1bW4oJ21vdGlmX2FsdF9pZCcpICU+JSAKICBsZWZ0X2pvaW4odGZfbW90aWYpICU+JSAKICBhcnJhbmdlKHB2YWx1ZSkgJT4lIAogIGRhdGEuZnJhbWUoKSAlPiUgCiAgbGVmdF9qb2luKFpfc2NvcmluZyAlPiUgIG11dGF0ZShUeXBlID0gY2FzZV93aGVuKGdyZXBsKCdHRlAnLCBzYW1wbGUpIH4gJ0dGUCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JlcGwoJ1JGUCcsIHNhbXBsZSkgfiAnUkZQJywKICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gJ2lQU0MnKSkgJT4lIAogICAgICAgICAgICAgIGZpbHRlcihUeXBlPT0nR0ZQJykgJT4lIGdyb3VwX2J5KG1vdGlmX2FsdF9pZCkgJT4lIHN1bW1hcmlzZShgWiBzY29yZWAgPSBtZWFuKGBaIHNjb3JlYCkpKSAlPiUgCiAgbGVmdF9qb2luKGdmcF9yZnAgJT4lIG11dGF0ZShURj1HZW5lLCBsb2cyRkNfR0ZQX1JGUF9STkFTZXEgPSBsb2cyRm9sZENoYW5nZSkgJT4lIHNlbGVjdChURiwgbG9nMkZDX0dGUF9SRlBfUk5BU2VxKSkgCnJlc3VsdHMgJT4lIGZpbHRlcigoYFogc2NvcmVgKSA+IDIuNSkgJT4lIGFycmFuZ2UocHZhbHVlKSAlPiUgRFQ6OmRhdGF0YWJsZSgpCmBgYAoKIyMgVm9sY2FubwpgYGB7cn0Kdm9sY2Fub19tYWtlciA8LSBmdW5jdGlvbihkZiwgdGl0bGUpewogIGRmJENsYXNzIDwtICdOb3Qgc2lnbmlmaWNhbnQnCiAgZGYkQ2xhc3NbZGYkcGFkaiA8IDAuMDEgJiBkZiRsb2cyRm9sZENoYW5nZSA+IDAuNCAmIChkZiRgWiBzY29yZWApID4gMi41XSA8LSAiRkRSIDwgMC4wMSAmXG5hYnMoWiBzY29yZSkgPiAyLjUiCiAgZGYkQ2xhc3NbZGYkcGFkaiA8IDAuMDEgJiBkZiRsb2cyRm9sZENoYW5nZSA8IC0wLjQgJiAoZGYkYFogc2NvcmVgKSA+IDIuNV0gPC0gIkZEUiA8IDAuMDEgJlxuYWJzKFogc2NvcmUpID4gMi41IgogIGRmJENsYXNzIDwtIGZhY3RvcihkZiRDbGFzcywgbGV2ZWxzPWMoJ05vdCBzaWduaWZpY2FudCcsICJGRFIgPCAwLjAxICZcbmFicyhaIHNjb3JlKSA+IDIuNSIpKQogIHBsb3QgPC0gZ2dwbG90KGRhdGE9ZGYsYWVzKHg9bG9nMkZvbGRDaGFuZ2UseT0tbG9nMTAocHZhbHVlKSkpICsgCiAgICBnZW9tX3BvaW50KGFlcyhjb2xvdXI9Q2xhc3MsIHNpemU9YFogc2NvcmVgKSwgYWxwaGE9MC41KSArCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jKCJncmF5IiwiZGFya3JlZCIpKSArIAogICAgZ2VvbV90ZXh0X3JlcGVsKGRhdGE9ZGYgJT4lIGZpbHRlcigocGFkaiA8IDAuMDEgJiBsb2cyRm9sZENoYW5nZSA+IDAuNCAmIGBaIHNjb3JlYCA+IDIuNSkgfCAocGFkaiA8IDAuMDEgJiBsb2cyRm9sZENoYW5nZSA8IC0wLjQgJiBgWiBzY29yZWAgPiAyLjUpKSwgCiAgICAgICAgICAgICAgICAgICAgYWVzKGxhYmVsPVRGKSkgKwogICAgIyBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PS0wLjUpLGxpbmV0eXBlPSJkb3R0ZWQiKSArCiAgICAjIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9MC41KSxsaW5ldHlwZT0iZG90dGVkIikgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1jKC0xLC0wLjUsMCwwLjUsMSkpICsKICAgIGdndGl0bGUodGl0bGUpICsgdGhlbWVfbWluaW1hbCgpCiAgcmV0dXJuKHBsb3QpCn0Kdm9sY2Fub19tYWtlcihyZXN1bHRzLCAnR0ZQIHZzIFJGUCBURkJTIG1vdGlmIGNvdW50cycpCgpgYGAKCgojIyBQbG90cyBvZiB0aGUgdG9wIG1vdGlmcyBkaWZmZXJlbnQgYmV0d2VlbiBHRlAgYW5kIFJGUApSRlAgSUkgRSBpcyBwbG90dGVkIGhlcmUsIGJ1dCBub3QgdXNlZCBpbiB0aGUgZGlmZmVyZW50aWFsIGFuYWx5c2lzIGFib3ZlCmBgYHtyLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD02fQpwbG90cyA8LSBsaXN0KCkKZm9yIChpIGluIHJlc3VsdHMgJT4lIGZpbHRlcigoYFogc2NvcmVgKSA+IDIpICU+JSBhcnJhbmdlKHB2YWx1ZSkgJT4lIGhlYWQobj0xMikgJT4lIHB1bGwobW90aWZfYWx0X2lkKSl7CiAgcGxvdHNbW2ldXSA8LSBwbG90dGVyKGksIHNjYWxlPVQpCn0KCgpwbG90X2dyaWQocGxvdGxpc3QgPSBwbG90cywgbmNvbD0zKQpgYGAKCgpgYGB7cn0KIyBHZW5lIDwtPiBtb3RpZiBtYXRjaGluZwojIGxvYWQgZ3RmIHRvIG1hcCB0cmFuc2NyaXB0IHRvIGdlbmVfbmFtZQoKZ3RmIDwtIHJlYWRfdHN2KCIvVm9sdW1lcy9kYXRhL2dlbm9tZXMvMTAwMEdfcGhhc2UyX0dSQ2gzNy9nZW5jb2RlLnYyOGxpZnQzNy5tZXRhZGF0YS5IR05DLmd6IiwgY29sX25hbWVzID0gYygnVHJhbnNjcmlwdCcsJ0dlbmUnKSkKIyBndGYgPC0gZ3RmICU+JSByb3d3aXNlKCkgJT4lIG11dGF0ZSh0cmFuc2NyaXB0ID0gKChzdHJfc3BsaXQoVjksICc7JylbWzFdXSAlPiUgc3RyX2V4dHJhY3QoJ3RyYW5zY3JpcHRfaWQuKicpICU+JSBuYS5vbWl0KCkpWzFdICU+JSBzdHJfc3BsaXQoLiwgJ1wiJykpW1sxXV1bMl0sCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lID0gKChzdHJfc3BsaXQoVjksICc7JylbWzFdXSAlPiUgc3RyX2V4dHJhY3QoJ2dlbmVfbmFtZS4qJykgJT4lIG5hLm9taXQoKSlbMV0gJT4lIHN0cl9zcGxpdCguLCAnXCInKSlbWzFdXVsyXSkKYGBgCgpgYGB7cn0KIyMgU2FtcGxlIGRhdGEKIyMjIENsb3Nlc3QgVFNTIGZvciBPVFgyIChNNTcwMF8xLjAyKQoKIyBzYW1wbGVzX2Z1bGwgPSBsaXN0LmZpbGVzKCcvVm9sdW1lcy9kYXRhL3Byb2plY3RzL25laS9odWZuYWdlbC9pUFNDX1JQRV9BVEFDX1NlcS9jbG9zZXN0X1RTU19tb3RpZnMvJywgCiMgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gJypkYXQuZ3onLCBmdWxsLm5hbWVzID0gVCkKIyAKIyBjbG9zZXN0VFNTX2RhdGEgPC0gbGlzdCgpCiMgZm9yIChpIGluIHNhbXBsZXNfZnVsbCl7CiMgICBzYW1wbGUgPC0gc3RyX3NwbGl0KHN0cl9zcGxpdChpLCAnLycpW1sxXV1bMTBdLCAnXFwuJylbWzFdXVsxXQojICAjIGlucHV0IDwtIHJlYWRfdHN2KGksIGNvbF9uYW1lcyA9IGMoJ3NlcXVlbmNlX25hbWUnLCAnc3RhcnQnLCAnZW5kJywgJ21vdGlmJywgJ2ZpbW9fcHZhbHVlJywgJ3N0cmFuZCcsICd0c3Nfc2VxJywndHNzX3N0YXJ0JywndHNzX2VuZCcsICd0cmFuc2NyaXB0JywgICdibGFuaycsJ3Rzc19zdHJhbmQnLCdjb29yZDEnLCdjb29yZDInLCdibGFuazInLCdleG9uX251bScsJ3NpemUnLCdleG9uX3BvcycsJ2Rpc3RhbmNlJykpCiMgICBpbnB1dCA8LSBmcmVhZChwYXN0ZSgnZ3pjYXQnLCBpLCAnfCBncmVwIE01NzAwXzEnKSkKIyAgIGNvbG5hbWVzKGlucHV0KSA8LSBjKCdzZXF1ZW5jZV9uYW1lJywgJ3N0YXJ0JywgJ2VuZCcsICdtb3RpZicsICdmaW1vX3B2YWx1ZScsICdzdHJhbmQnLCAndHNzX3NlcScsJ3Rzc19zdGFydCcsJ3Rzc19lbmQnLCAndHJhbnNjcmlwdCcsICAnYmxhbmsnLCd0c3Nfc3RyYW5kJywnY29vcmQxJywnY29vcmQyJywnYmxhbmsyJywnZXhvbl9udW0nLCdzaXplJywnZXhvbl9wb3MnLCdkaXN0YW5jZScpCiMgICBpbnB1dCA8LSBpbnB1dCAlPiUgbXV0YXRlKHNlcXVlbmNlX25hbWUgPSBhcy5jaGFyYWN0ZXIoc2VxdWVuY2VfbmFtZSksCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRzc19zZXEgPSBhcy5jaGFyYWN0ZXIodHNzX3NlcSksCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvb3JkMT1hcy5udW1lcmljKGNvb3JkMSksCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvb3JkMj1hcy5udW1lcmljKGNvb3JkMiksCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJsYW5rMiA9IGFzLmNoYXJhY3RlcihibGFuazIpLAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICBleG9uX251bT1hcy5pbnRlZ2VyKGV4b25fbnVtKSwKIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZT1hcy5udW1lcmljKHNpemUpKSAlPiUgbXV0YXRlKHNhbXBsZSA9IHNhbXBsZSkKIyAgIGNsb3Nlc3RUU1NfZGF0YVtbc2FtcGxlXV0gPC0gaW5wdXQKIyB9CiMgc2FtcGxlX2Nsb3Nlc3RUU1MgPSBiaW5kX3Jvd3MoY2xvc2VzdFRTU19kYXRhKSAlPiUgbXV0YXRlKHNhbXBsZT1hcy5mYWN0b3Ioc2FtcGxlKSkgJT4lIHJvd3dpc2UoKSAlPiUgbXV0YXRlKFRyYW5zY3JpcHQgPSBzdHJfc3BsaXQodHJhbnNjcmlwdCwgJ18nKVtbMV1dWzFdKQojIAojIGJvdGggPC0gbGVmdF9qb2luKHNhbXBsZV9jbG9zZXN0VFNTLCBndGYpICU+JSAKIyAgIGZpbHRlcighaXMubmEoR2VuZSkpICU+JSAKIyAgIG11dGF0ZShtb3RpZl9sb2MgPSBwYXN0ZShzZXF1ZW5jZV9uYW1lLCBzdGFydCwgZW5kLCBzZXA9J18nKSkKIyAKIyBib3RoICU+JSAKIyAgICMgcmVtb3ZlIGFueSBUU1Mgb3ZlciA1MDBrYiBhd2F5CiMgICBmaWx0ZXIoZGlzdGFuY2UgPCA1MDAwMDApICU+JSAKIyAgICMgIG9uZSBnZW5lIHBlciBtb3RpZgojICAgZ3JvdXBfYnkobW90aWZfbG9jLCBzYW1wbGUsIEdlbmUpICU+JSAKIyAgIHRvcF9uKDEsIGRpc3RhbmNlKSAlPiUgCiMgICB1bmdyb3VwKCkgJT4lIAojICAgIyBhZGQgdXAgdG8gdHdvIGdlbmVzICh0b3RhbCkgcGVyIG1vdGlmCiMgICBncm91cF9ieShtb3RpZl9sb2MsIHNhbXBsZSkgJT4lIAojICAgdG9wX24oMiwgZGlzdGFuY2UpICU+JSAKIyAgIHVuZ3JvdXAoKSAlPiUgCiMgICAjIGFycmFuZ2UgYnkgZ2VuZXMgbW9zdCBsaW5rZWQgdG8gbW90aWYgIAojICAgZ3JvdXBfYnkoR2VuZSwgc2FtcGxlKSAlPiUgCiMgICBzdW1tYXJpc2UoQ291bnQ9bigpLCBwYXN0ZShtb3RpZl9sb2MsIGNvbGxhcHNlPScsICcpKSAlPiUgCiMgICBhcnJhbmdlKC1Db3VudCkKYGBgCgpgYGB7cn0KCiMgV2hhdCBnZW5lcyBoYXZlIG1vcmUgUEFYNiAnYXNzb2NpYXRlZCcgbW90aWZzIGNvbXBhcmVkIGZyb20gR0ZQIHRvIFJGUAoKIyBlbnJpY2hlZF9nZW5lcyA8LSBib3RoICU+JSAKIyAgICMgb25seSBrZWVwIG9uZSBnZW5lIHBlciBtb3RpZgojICAgZ3JvdXBfYnkobW90aWZfbG9jLCBzYW1wbGUsIEdlbmUpICU+JSAKIyAgIHRvcF9uKDEsIGRpc3RhbmNlKSAlPiUgCiMgICB1bmdyb3VwKCkgJT4lIAojICAgIyBrZWVwIHVwIHRvIHR3byBnZW5lcyBwZXIgbW90aWYKIyAgIGdyb3VwX2J5KG1vdGlmX2xvYywgc2FtcGxlKSAlPiUgCiMgICB0b3BfbigyLCBkaXN0YW5jZSkgJT4lIAojICAgdW5ncm91cCgpICU+JSAKIyAgICMgYXJyYW5nZSBieSBnZW5lcyBtb3N0IGxpbmtlZCB0byBtb3RpZiAgCiMgICBncm91cF9ieShHZW5lLCBzYW1wbGUpICU+JSAKIyAgIHN1bW1hcmlzZShDb3VudD1uKCksIHBhc3RlKG1vdGlmX2xvYywgY29sbGFwc2U9JywgJykpICU+JSAKIyAgIHVuZ3JvdXAoKSAlPiUgCiMgICAjIGNvbGxhcHNlIHRvIEdGUC9SRlAvaVBTQwojICAgbXV0YXRlKFR5cGUgPSBjYXNlX3doZW4oZ3JlcGwoJ0dGUCcsIHNhbXBsZSkgfiAnR0ZQJywKIyAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyZXBsKCdSRlAnLCBzYW1wbGUpIH4gJ1JGUCcsCiMgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gJ2lQU0MnKSkgJT4lIAojICAgZ3JvdXBfYnkoR2VuZSwgVHlwZSkgJT4lIAojICAgc3VtbWFyaXNlKFRvdGFsPXN1bShDb3VudCkpICU+JSAKIyAgIGFycmFuZ2UoLVRvdGFsKSAlPiUgCiMgICBzcHJlYWQoR2VuZSwgVG90YWwpICU+JSB0KCkgCiMgCiMgY29sbmFtZXMoZW5yaWNoZWRfZ2VuZXMpIDwtIGVucmljaGVkX2dlbmVzWzEsXQojIGVucmljaGVkX2dlbmVzIDwtIGVucmljaGVkX2dlbmVzWy0xLF0gJT4lIGRhdGEuZnJhbWUoKSAlPiUgcm93bmFtZXNfdG9fY29sdW1uKCdHZW5lJykgJT4lICBtdXRhdGUoR0ZQID0gYXMubnVtZXJpYyhHRlApLCBpUFNDID0gYXMubnVtZXJpYyhpUFNDKSwgUkZQID0gYXMubnVtZXJpYygoUkZQKSkpCiMgCiMgZW5yaWNoZWRfZ2VuZXNbaXMubmEoZW5yaWNoZWRfZ2VuZXMpXSA8LSAwCiMgCiMgZW5yaWNoZWRfZ2VuZXMgJT4lIG11dGF0ZShgZGVsdGFHRlAgPC0+IFJGUGAgPSBHRlAgLSBSRlApICU+JSBhcnJhbmdlKC1gZGVsdGFHRlAgPC0+IFJGUGAsIEdGUCkgJT4lIGhlYWQoMTAwMCkgJT4lIERUOjpkYXRhdGFibGUocm93bmFtZXMgPSBGKQoKYGBgCgoKYGBge3J9CiMjIGRvZXMgUEFYNiByZWd1bGF0ZSBQQVg2PwojWWVzLCB5ZXMgaXQgZG9lcy4KCiNHRlAgc3BlY2lmaWMhCiAgCiMgYm90aCAlPiUgCiMgICAjIG9ubHkga2VlcCBvbmUgZ2VuZSBwZXIgbW90aWYKIyAgIGdyb3VwX2J5KG1vdGlmX2xvYywgc2FtcGxlLCBHZW5lKSAlPiUgCiMgICB0b3BfbigxLCBkaXN0YW5jZSkgJT4lIAojICAgdW5ncm91cCgpICU+JSAKIyAgICMga2VlcCB1cCB0byB0d28gZ2VuZXMgcGVyIG1vdGlmCiMgICBncm91cF9ieShtb3RpZl9sb2MsIHNhbXBsZSkgJT4lIAojICAgdG9wX24oMiwgZGlzdGFuY2UpICU+JSAKIyAgIHVuZ3JvdXAoKSAlPiUgCiMgICAjIGFycmFuZ2UgYnkgZ2VuZXMgbW9zdCBsaW5rZWQgdG8gbW90aWYgIAojICAgZ3JvdXBfYnkoR2VuZSwgc2FtcGxlKSAlPiUgCiMgICBzdW1tYXJpc2UoQ291bnQ9bigpLCBgTW90aWYgTG9jYXRpb25zYCA9IHBhc3RlKG1vdGlmX2xvYywgY29sbGFwc2U9JywgJykpICU+JSAKIyAgIGFycmFuZ2UoLUNvdW50KSAlPiUgCiMgICBmaWx0ZXIoR2VuZT09J1BBWDYnKQpgYGAKCmBgYAo=